/*
 Dijjer - A Peer to Peer HTTP Cache
 Copyright (C) 2004,2005  Change.Tv, Inc

 This program is free software; you can redistribute it and/or modify
 it under the terms of the GNU General Public License as published by
 the Free Software Foundation; either version 2 of the License, or
 (at your option) any later version.

 This program is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 GNU General Public License for more details.

 You should have received a copy of the GNU General Public License
 along with this program; if not, write to the Free Software
 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */
package dijjer;

import dijjer.io.comm.*;
import dijjer.io.download.*;
import dijjer.io.store.*;
import dijjer.io.xfer.http.*;
import dijjer.updater.*;
import dijjer.util.logging.*;

import java.io.*;
import java.net.*;
import java.util.*;

public class Dijjer {

	public static final String VERSION = "$Id: Dijjer.java 37 2009-04-02 18:37:02Z mathias.demare $";
	// Define some universal constants
	public static final int PACKET_SIZE = 1024;
	public static final int PACKETS_IN_BLOCK = 256;
	public static int BUILD = 120;
	public static int FIRST_GOOD_BUILD = 105;
	public static final int JOINTTL = 10;
	public static final int DEFAULT_EXTERNAL_PORT = 9114;
	public static final int DEFAULT_LOCAL_PORT = 9115;
	public static final int DEFAULT_DATASTORE_SIZE = 256;
	public static final int DEFAULT_ROUTING_TABLE_SIZE = 15;
	public static final int SEED_NODE_ROUTING_TABLE_SIZE = 25;
	public static final int DEFAULT_HASHSTORE_SIZE = 100;
	// Preferences
	protected HashSet _seedNodes = new HashSet();
	protected LinkedList _initPeers = new LinkedList();
	private static final int DEFAULT_THREAD_COUNT = 6;
	private int _externalListenPort = DEFAULT_EXTERNAL_PORT;
	private int _localListenPort = DEFAULT_LOCAL_PORT;
	private long _dataStoreSize = DEFAULT_DATASTORE_SIZE;
	private int _routingTableSize = DEFAULT_ROUTING_TABLE_SIZE;
	private boolean _disableRapidPing = false;
	private boolean _useLocalNetwork = false;
	private String _localNetwork = "";
	private int _downloaderThreads = DEFAULT_THREAD_COUNT;
	private PrintWriter _dumpMessageWaitTimes = null;
	private static Dijjer _dijjer;

	public Dijjer() {
		DMT.init();
		try {
			_seedNodes.add(AbstractFactory.getFactory().getPeer("seed1.dijjer.org:9114"));
		}
		catch(NoSuchElementException e) {
			Logger.warning("Could not parse String to WebPeer");
		}
		catch(UnknownHostException e) {
			Logger.warning("Couldn't perform DNS lookup on WebPeer " + "seed1.dijjer.org:9114"
					+ ", are we connected to the Internet? (" + e + ")");
		}
		catch(Exception e) {
			Logger.error("Unexpected error thrown while parsing peer!", e);
		}
		try {
			_seedNodes.add(AbstractFactory.getFactory().getPeer("dijjer.volkmesh.net:9114"));
		}
		catch(NoSuchElementException e) {
			Logger.warning("Could not parse String to WebPeer");
		}
		catch(UnknownHostException e) {
			Logger.warning("Couldn't perform DNS lookup on WebPeer " + "dijjer.volkmesh.net:9114"
					+ ", are we connected to the Internet? (" + e + ")");
		}
		catch(Exception e) {
			Logger.error("Unexpected error thrown while parsing peer!", e);
		}
		try {
			_seedNodes.add(AbstractFactory.getFactory().getPeer("dijjer.chipped.net:4119"));
		}
		catch(NoSuchElementException e) {
			Logger.warning("Could not parse String to WebPeer");
		}
		catch(UnknownHostException e) {
			Logger.warning("Couldn't perform DNS lookup on WebPeer " + "dijjer.chipped.net:4119"
					+ ", are we connected to the Internet? (" + e + ")");
		}
		catch(Exception e) {
			Logger.error("Unexpected error thrown while parsing peer!", e);
		}
		try {
			_seedNodes.add(AbstractFactory.getFactory().getPeer("gambit.uunode.net:4119"));
		}
		catch(NoSuchElementException e) {
			Logger.warning("Could not parse String to WebPeer");
		}
		catch(UnknownHostException e) {
			Logger.warning("Couldn't perform DNS lookup on WebPeer " + "gambit.uunode.net:4119"
					+ ", are we connected to the Internet? (" + e + ")");
		}
		catch(Exception e) {
			Logger.error("Unexpected error thrown while parsing peer!", e);
		}
		try {
			_seedNodes.add(AbstractFactory.getFactory().getPeer("choco.homelinux.org:9114"));;
		}
		catch(NoSuchElementException e) {
			Logger.warning("Could not parse String to WebPeer");
		}
		catch(UnknownHostException e) {
			Logger.warning("Couldn't perform DNS lookup on WebPeer " + "choco.homelinux.org:9114"
					+ ", are we connected to the Internet? (" + e + ")");
		}
		catch(Exception e) {
			Logger.error("Unexpected error thrown while parsing peer!", e);
		}
	}

	public boolean start(String[] args) throws Exception {
		Logger.init("Starting Dijjer build " + BUILD + " (c) Copyright 2004, 2005 Change.Tv, Inc.");
		Logger.init("Distributed under the Gnu Public License");
		DataStore.init(new File(AbstractFactory.getFactory().getResourceManager().getDataDirectory(), "index-" + _externalListenPort + "."
				+ PACKETS_IN_BLOCK + "k.dij"), new File(AbstractFactory.getFactory().getResourceManager().getDataDirectory(), "data-"
				+ _externalListenPort + "." + PACKETS_IN_BLOCK + "k.dij"),
				(_dataStoreSize * (1024 * 1024) / Store.DATA_BLOCK_SIZE));
		HashStore.init(new File(AbstractFactory.getFactory().getResourceManager().getDataDirectory(), "hash-" + _externalListenPort + "."
				+ PACKETS_IN_BLOCK + "k.dij"), DEFAULT_HASHSTORE_SIZE);
		AbstractFactory.getFactory().initUdpSocketManager(_externalListenPort);
		AbstractFactory.getFactory().initRoutingTable(_routingTableSize);
		Dispatcher.init();
		AbstractFactory.getFactory()
			.initMulticastSocketManager(AbstractFactory.getFactory().getResourceManager().getMulticastPort());
		DownloaderThread.init();
		HTTPServer.initServer(_localListenPort);
		connectToNetwork();
		return true;
	}

	protected boolean _connecting = false;

	public void connectToNetwork() throws Exception {
		for (Iterator i = _initPeers.iterator(); i.hasNext();) {
			AbstractPeer peer = (AbstractPeer) i.next();
			Logger.info("Adding peer " + peer);
			AbstractFactory.getFactory().getRoutingTable().addPeer(peer);
		}
		if (_seedNodes.size() == 0) {
			// Don't seed if we are a seed node
			return;
		}
		if (_connecting) { // Prevent multiple simultaenous reconnections
			return;
		}
		ArrayList nonSeedPeers = AbstractFactory.getFactory().getRoutingTable().getPeers();
		nonSeedPeers.removeAll(_seedNodes);
		int numNonSeedPeers = nonSeedPeers.size();
		if (numNonSeedPeers > 0) {
			return;
		}
		_connecting = true;
		Logger.info("Connecting to the network");
		(new Thread() {

			public void run() {
				try {
					long delay = 10;
					while (true) {
						LinkedList seeds = new LinkedList(_seedNodes);
						Collections.shuffle(seeds);
						int numSuccessfulSeeds = 0;
						for (Iterator i = seeds.iterator(); i.hasNext();) {
							AbstractPeer seed = (AbstractPeer) i.next();
							Logger.info("Attempting to assimilate through " + seed);
							if (!AbstractFactory.getFactory().getRoutingTable().joinSeed(seed)) {
								Logger.warning("Failed to seed using " + seed);
							} else {
								numSuccessfulSeeds++;
							}
							if (numSuccessfulSeeds >= 2) {
								break;
							}
						}

// This is killing off clients that could just fetch the link directly.
//						
//						if (numSuccessfulSeeds == 0) {
//							ArrayList nonSeedPeers = RoutingTable.getRoutingTable().getPeers();
//							nonSeedPeers.removeAll(_seedNodes);
//							int numNonSeedPeers = nonSeedPeers.size();
//							if (numNonSeedPeers == 0) {
//								Logger.fatal(-1, "Failed to contact any of my seednodes and no peers in RT");
//							}
//						} else {
						if (numSuccessfulSeeds != 0) {
							ArrayList nonSeedPeers = AbstractFactory.getFactory().getRoutingTable().getPeers();
							nonSeedPeers.removeAll(_seedNodes);
							int numNonSeedPeers = nonSeedPeers.size();
							if (numNonSeedPeers > 0) {
								Logger.info("Acquired " + numNonSeedPeers + " non-seed peers during join");
								break;
							}
						}
						Logger
								.warning("No new peers were contacted, waiting " + delay
										+ " seconds before trying again");
						Thread.sleep(delay * 1000);
						if (delay < 600) {
							delay = delay * 2;
						}
					}
				} catch (Exception e) {
					throw new RuntimeException(e);
				}
				_connecting = false;
			};
		}).start();
	}

	public void shutdown(boolean exit) {
		try {
			AbstractFactory.getFactory().getResourceManager().softShutdown();
			AbstractFactory.getFactory().getRoutingTable().shutdown();
			HashStore.getHashStore().closeStore();
			DataStore.getDataStore().closeStore();
			AbstractFactory.getFactory().getUdpSocketManager().shutdownThread();
			HTTPServer.getHTTPServer().shutdownThread();
			if (exit) {
				AbstractFactory.getFactory().getResourceManager().shutdown();
				System.exit(0);
			}
		} catch (IOException e) {
			Logger.fatal(-1, "Exception " + e + " while shutting down");
		}
	}
	
	public String getLocalNetwork() {
		return _localNetwork;
	}

	public Set getSeedNodes() {
		return _seedNodes;
	}

	public int getExternalListenPort() {
		return _externalListenPort;
	}
	
	public boolean getUseLocalNetwork() {
		return _useLocalNetwork;
	}

	public boolean isRapidPingDisabled() {
		return _disableRapidPing;
	}
	
	public void addSeedNode(AbstractPeer peer) {
		_seedNodes.add(peer);
	}
	
	public void addPeer(AbstractPeer peer) {
		_seedNodes.add(peer);
	}

	public void setDataStoreSize(int dataStoreSize) {
		_dataStoreSize = dataStoreSize;
	}

	public void setExternalListenPort(int externalListenPort) {
		_externalListenPort = externalListenPort;
	}

	public void setLocalListenPort(int localListenPort) {
		_localListenPort = localListenPort;
	}

	public void setRoutingTableSize(int routingTableSize) {
		_routingTableSize = routingTableSize;
	}

	public void setDisableRapidPing(boolean disableRapidPing) {
		_disableRapidPing = disableRapidPing;
	}

	public void setDownloadThreadCount(int threads) {
		_downloaderThreads = threads;
	}
	
	public void setUseLocalNetwork(boolean useLocalNetwork) {
		_useLocalNetwork = useLocalNetwork;
	}
	
	public void setLocalNetwork(String localNetwork) {
		_localNetwork = localNetwork;
	}

	public static void init() {
		_dijjer = new Dijjer();
	}

	public static Dijjer getDijjer() {
		if(_dijjer == null)
			init();
		return _dijjer;
	}

	public PrintWriter getDumpMessageWaitTimes() {
		return _dumpMessageWaitTimes;
	}

	public void setDumpMessageWaitTimes(PrintWriter dumpMessageWaitTimes) {
		_dumpMessageWaitTimes = dumpMessageWaitTimes;
	}
}
